.com
Hosted by:
Unit testing expertise at your fingertips!
Home | Discuss | Lists

Prebuilt Fixture

The book has now been published and the content of this chapter has likely changed substanstially.
Please see page 429 of xUnit Test Patterns for the latest information.
Also known as: Prebuilt Context, Test Bed

How do we cause the Shared Fixture to be built before the first test method that needs it?

Build the Shared Fixture separately from running the tests.

Sketch Prebuilt Fixture embedded from Prebuilt Fixture.gif

When we have chosen to use a Shared Fixture (page X) whether it be for reasons of convenience or necessity, we need to create it before it is used.

How It Works

We create the fixture sometime before running the test suite. We can create the fixture a number of different ways that we'll discuss in later; the most important thing is that we don't need to build the fixture each time the test suite is run because the fixture outlives both the mechanism used to build it and any one test suite that uses it.

When To Use It

We can reduce the overhead of creating a Shared Fixture each time a test suite is running by only creating it occasionally. This is especially appropriate when the cost of constructing the Shared Fixture is extremely high.

Because of the Manual Intervention (page X) to (re)build the fixture before the tests are run, we'll probably end up using the same fixture multiple times and this can lead to Erratic Tests (page X) caused by shared fixture pollution. We may be able to avoid these by treating the Prebuilt Fixture as an Immutable Shared Fixture (see Shared Fixture) and build a Fresh Fixture (page X) for anything we plan to modify.

The alternatives to a Prebuilt Fixture are a Shared Fixture built once per test run or a Fresh Fixture. Shared Fixtures can be constructed using SuiteFixture Setup (page X), Lazy Setup (page X) or Setup Decorator (page X). Fresh Fixtures can be constructed using Inline Setup (page X), Implicit Setup (page X) or Delegated Setup (page X).

Variation: Global Fixture

A Global Fixture is a special case of Prebuilt Fixture where we shared the fixture between multiple test automaters. The key difference is that the fixture is globally visible and not "private" to a particular user. This is most common when using a single shared Database Sandbox (page X) without using some form of Database Partitioning Scheme (see Database Sandbox).

The tests themselves can be the same as for a basic Prebuilt Fixture and the fixture setup is the same as for Prebuilt Fixture. What's different is the kinds of problems we can run into. Because the fixture is now shared amongst multiple users each running a separate Test Runner (page X) on a different CPU, we can run into all sorts of multi-processing related issues. The most common is the Test Run War (see Erratic Test) where we see seemingly random results. This can be avoided by adopting some kind of Database Partitioning Scheme or by using Distinct Generated Values (see Generated Value on page X) for any fields with unique key constraints.

Implementation Notes

The tests themselves look identical to a basic Shared Fixture. What's different is how the fixture is set up. The test reader won't be able to find any sign of it within the Testcase Class (page X) nor in a Setup Decorator or SuiteFixture Setup method. It is most probably done manually via some kind of database copy operation, using a Data Loader (see Back Door Manipulation on page X) or by running a database population script. These are all examples of Back Door Setup (see Back Door Manipulation) wherein we bypass the system under test (SUT) and interact with it's database directly. (See the sidebar Database as SUT API? (page X) for an example of when the back door really is a front door.) Another option is to use a Fixture Setup Testcase (see Chained Tests on page X) run from a Test Runner either manually or regularly scheduled.

Another difference is how the Finder Methods (see Test Utility Method on page X) are implemented. We cannot just store the results of creating the objects in a class variable or Test Fixture Registry (see Test Helper on page X) because we aren't setting the fixture up in code within the test run. That means we need to either store the unique identifies generated during fixture construction in a file as we build the fixture so that the Finder Methods can retrieve them later, or we just hard-code the identifiers in the Finder Methods. We could also search for objects/records that meet the Finder Methods' criteria at runtime but that could result in Nondeterministic Tests (see Erratic Test) because each test run could end up using a different object/record from the Prebuilt Fixture. That may be a good idea if each test run modifies the objects such that they no longer satisfy the criteria but it may make debugging a failing test rather difficult especially if the failures are intermittent because some other attribute of the selected object is different.

Motivating Example

This example shows the construction of a Shared Fixture using Lazy Setup(Of course, there are other ways to set up the Shared Fixture such as Setup Decorator or SuiteFixture Setup.).

   protected void setUp() throws Exception {
      if (sharedFixtureInitialized) {
         return;
      }
      facade = new FlightMgmtFacadeImpl();
      setupStandardAirportsAndFlights();
      sharedFixtureInitialized = true;
   }
  
   protected void tearDown() throws Exception {
      // Cannot delete any objects because we don't know
      // whether or not this is the last test
   }
Example LazyFixtureInitialization embedded from java/com/clrstream/ex6/services/test/SharedFixtureFlightManagementFacadeTest.java

Note the call to setupStandardAirports in the setUp method. The tests use this fixture by calling Finder Methods that return objects from the fixture that match certain criteria:

   public void testGetFlightsByFromAirport_OneOutboundFlight() throws Exception {
      FlightDto outboundFlight = findOneOutboundFlight();
      // Exercise System
      List flightsAtOrigin = facade.getFlightsByOriginAirport(
                           outboundFlight.getOriginAirportId());
      // Verify Outcome
      assertOnly1FlightInDtoList( "Flights at origin", outboundFlight,
                                  flightsAtOrigin);
   }
  
   public void testGetFlightsByFromAirport_TwoOutboundFlights() throws Exception {
      FlightDto[] outboundFlights = findTwoOutboundFlightsFromOneAirport();
      // Exercise System
      List flightsAtOrigin = facade.getFlightsByOriginAirport(
                      outboundFlights[0].getOriginAirportId());
      // Verify Outcome
      assertExactly2FlightsInDtoList( "Flights at origin", outboundFlights,
                                      flightsAtOrigin);
   }
Example SharedTestFixture embedded from java/com/clrstream/ex6/services/test/SharedFixtureFlightManagementFacadeTest.java

Refactoring Notes

One way to convert a Testcase Class from a Standard Fixture (page X) to a Prebuilt Fixture is to do a Extract Class[Fowler] refactoring so that the fixture is set up in one class and the Test Methods (page X) are located in another. Of course, we need to provide a way for the Finder Methods to know what objects or records exist in the structure since we won't be able to guarantee that any instance or class variables will bridge the time gap between fixture construction and fixture usage.

Example: Prebuilt Fixture Test

Here is the resulting Testcase Class that contains the Test Methods. What is most notable is that is looks pretty much identical to the basic Shared Fixture tests:

   public void testGetFlightsByFromAirport_OneOutboundFlight() throws Exception {
      FlightDto outboundFlight = findOneOutboundFlight();
      // Exercise System
      List flightsAtOrigin = facade.getFlightsByOriginAirport(
                           outboundFlight.getOriginAirportId());
      // Verify Outcome
      assertOnly1FlightInDtoList( "Flights at origin", outboundFlight,
                                  flightsAtOrigin);
   }
  
   public void testGetFlightsByFromAirport_TwoOutboundFlights() throws Exception {
      FlightDto[] outboundFlights = findTwoOutboundFlightsFromOneAirport();
      // Exercise System
      List flightsAtOrigin = facade.getFlightsByOriginAirport(
                      outboundFlights[0].getOriginAirportId());
      // Verify Outcome
      assertExactly2FlightsInDtoList( "Flights at origin", outboundFlights,
                                      flightsAtOrigin);
   }
Example PrebuiltFixtureUsage embedded from java/com/clrstream/ex6/services/test/SharedFixtureFlightManagementFacadeTest.java

What's different is how the fixture is set up and how the Finder Methods are implemented.

Example: Fixture Setup Testcase

We may find it to be convenient to set up our Prebuilt Fixture using xUnit. This is simple to do if we already have the appropriate Creation Methods (page X) or constructors already defined and a way to easily persist the objects into the Database Sandbox. Here's an example where we call the same method as in the previous example from the setUp method except that now it lives in the setUp method of a Fixture Setup Testcase that can be run whenever we want to regenerate the Prebuilt Fixture:

public class FlightManagementFacadeSetupTestcase
         extends AbstractFlightManagementFacadeTestCase {
   public FlightManagementFacadeSetupTestcase(String name) {
      super(name);
   }

   protected void setUp() throws Exception {
      facade = new FlightMgmtFacadeImpl();
      helper = new FlightManagementTestHelper();
      setupStandardAirportsAndFlights();
      saveFixtureInformation();
   }

   protected void tearDown() throws Exception {
      // Leave the Prebuilt Fixture for later use
   }
  
}
Example FixtureSetupTestcase embedded from java/com/clrstream/ex6/services/test/FlightManagementFacadeSetupTestcase.java

Note how there are no Test Methods on this Testcase Class and how the tearDown method is empty. This is because we want to do only the set up and nothing else.

Once we created the objects we saved the information to the database using the call to saveFixtureInformation; it persists the objects as well as saving the various keys in a file so that we can reload them for use from the subsequent real test runs. This avoids the need to hard-code knowledge of the fixture into Test Methods or Test Utility Methods. In the interest of space I'll spare the details of how we find the "dirty" objects and save the key information; there is more than one way to do this and any of them will suffice.

Example: Prebuilt Fixture Setup Using Data Population Script

There are more ways to build a Prebuilt Fixture in a Database Sandbox as there are programming languages. Everything from SQL scripts to Pearl or Ruby programs. The scripts can contain the data or they can read the data from a collection of flat files. We can even copy the contents of a "golden" database into our Database Sandbox. There really is no point showing any examples so I'll leave it as an exercise for the reader to figure out what's most appropriate in their particular circumstance.



Page generated at Wed Feb 09 16:39:39 +1100 2011

Copyright © 2003-2008 Gerard Meszaros all rights reserved

All Categories
Introductory Narratives
Web Site Instructions
Code Refactorings
Database Patterns
DfT Patterns
External Patterns
Fixture Setup Patterns
Fixture Teardown Patterns
Front Matter
Glossary
Misc
References
Result Verification Patterns
Sidebars
Terminology
Test Double Patterns
Test Organization
Test Refactorings
Test Smells
Test Strategy
Tools
Value Patterns
XUnit Basics
xUnit Members
All "Fixture Setup Patterns"
Fresh Fixture Setup:
--Inline Setup
--Delegated Setup
----Creation Method
--Implicit Setup
Shared Fixture Construction:
--Prebuilt Fixture
----Prebuilt Context
----Test Bed
----Global Fixture
--Lazy Setup
--SuiteFixture Setup
--Setup Decorator
--Chained Tests